home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Science⁄Math / VideoToolbox / VideoToolboxSources / Luminance.note < prev    next >
Encoding:
Text File  |  1993-12-16  |  22.5 KB  |  448 lines  |  [TEXT/KAHL]

  1. Luminance.note
  2. This file documents Luminance.c.
  3.  
  4. HISTORY:
  5. 3/11/92 dgp added prototypes to supplement the explanations of the routines.
  6.  
  7. INTRODUCTION:
  8.  
  9. The purpose of these subroutines is accurate control of contrast of visual 
  10. stimuli for vision experiments. These subroutines set up the Color Lookup Table 
  11. (CLUT) of a video framestore on the Mac II (e.g. the Apple Mac II Color Video 
  12. Card) to properly drive a monochrome monitor (e.g. the Apple High Resolution 
  13. Monochrome Monitor) via an Institute for Sensory Research (ISR) Video 
  14. Attenuator. The ISR Video Attenuator consists of resistive dividers which 
  15. attenuate the three color signal from the video framestore by different 
  16. amounts and combine them to produce one monochrome signal.
  17.  
  18. The video attenuator and the theory behind the algorithms described here are
  19. the topic of a paper:
  20. D.G. Pelli and L. Zhang (1991) Accurate control of contrast on microcomputer displays. 
  21. Vision Research, 31:1337-1360.
  22.  
  23. Achieving the goal of accurate control of contrast required solving five 
  24. problems:
  25. 1. The luminance of the monitor is nonlinearly related to the input voltage
  26. which depends linearly on the triplet loaded into the CLUT. This is solved by
  27. allowing the user to provide a polynomial (I recommend 4th order) describing the
  28. nonlinear relationship between luminance and nominal voltage. (For computational
  29. reasons, you must also supply a quadratic fit.) The subroutines LToV and VToL
  30. use this polynomial to convert back and forth.
  31. 2. Affordable framestores for microcomputers have 8 bit Digital to Analog
  32. Converters (DACs), which can only represent 256 different luminances. This is
  33. not enough precision to produce threshold-contrast patterns, which are important
  34. for vision experiments. This is solved by the ISR Video Attenuator which
  35. combines the outputs of the three DACs ("red", "green", and "blue") with various
  36. attenuations to yield one monochrome signal which can represent low contrasts
  37. (by varying an attenuated DAC) and high contrasts (by varying an unattenuated
  38. DAC).
  39. 3. DACs are only accurate to ± a least significant bit (LSB). The Brooktree DACs
  40. used by Apple and RasterOps are specified to have a ±1 LSB "integrated
  41. linearity" error, which means that the voltage produced by any number will
  42. deviate from a line connecting the 0 and 255 points by at most 1 LSB, which is
  43. defined as 1/255 times the voltage difference between 0 and 255. I assume a
  44. slightly stronger restriction will hold, that the difference between any two
  45. settings will be in error by at most one LSB. So it is important to allow the
  46. user to specify what range of luminances will be used to present a particular
  47. stimulus and to fix the coarsest DACs (i.e. those with the least attenuation in
  48. the ISR Video Attenuator) and only vary the fine DACs to produce the specified
  49. luminances. The range setting is done by SetLuminanceRange() which is called
  50. automatically by SetLuminance() and SetLuminances().
  51. 4. Apple's software environment for graphics, QuickDraw, isn't designed for
  52. vision experiments. The facilities for creating images are fine, but the control
  53. of the CLUTs provided by the Palette and Color Managers is unsatisfactory for
  54. vision research because a lot happens behind your back. In particular the
  55. Palette Manager likes to maintain a consistent color environment across the
  56. entire desktop, which includes all your screens. Well, in vision experiments we
  57. generally want to treat each screen as a totally separate device, unaffected by
  58. what we do to the other screens. This is solved by the routine GDSetEntries()
  59. which makes a low-level call to the video driver to change the CLUT directly
  60. without QuickDraw's knowledge. (This will work with any video card that works
  61. with the Mac II.) The routine LoadLuminances(), which is used below, is just a
  62. convenient glue routine for calling GDSetEntries().
  63. 5. Apple specifies that the video driver should silently implement gamma
  64. correction to attempt to correct for the display's nonlinear relation between
  65. luminance and input voltage. We don't want this because this hidden gamma
  66. correction loses precision and interferes with the operation of the ISR Video
  67. Attenuator, which will seem to operate NONlinearly if this gamma correction
  68. takes place unbeknownst to us. This is solved by the routine GDLinearGamma()
  69. which makes a low-level call to the video driver and loads a linear gamma table,
  70. i.e. no correction.
  71.  
  72. SUMMARY:
  73.  
  74. double SetLuminance(GDHandle myGDHandle,luminanceRecord *LP
  75.     ,int theEntry,double luminance
  76.     ,double lowLuminance,double highLuminance)
  77. Set one entry in the ColorSpec table (and the CLUT if myGDHandle is not NULL) to
  78. a specified luminance. It's ok for lowLuminance to be greater than highLuminance.
  79. SetLuminance() sets a single entry to the specified luminance. You must also
  80. indicate the luminance range that you are working over, to allow optimal choice
  81. of which dacs to fix, etc., to yield minimum error in relative luminance. 
  82. SetLuminances() does its work by making a call to SetLuminance() for each entry.
  83. SetLuminance takes 1 ms to set up the range (by calling SetLuminanceRange), which
  84. it only has to do once. Repeated calls to SetLuminance with the same range will
  85. not cause re-computation of the range. Once the range is set, SetLuminance takes
  86. about 0.1 ms. If SetLuminance is too slow for a real-time application you can tell
  87. it to just compute, but not load the new ColorSpec table (just supply NULL in place
  88. of myGDHandle), and you can then quickly load your ColorSpec table into the CLUT
  89. later by calling LoadLuminances().
  90.  
  91. double SetLuminancesAndRange(GDHandle myGDHandle,luminanceRecord *LP
  92.     ,int firstEntry,int lastEntry
  93.     ,double firstLuminance,double lastLuminance
  94.     ,double lowLuminance,double highLuminance)
  95. /*
  96. Set a series of entries in the ColorSpec table (and the CLUT if myGDHandle is not NULL)
  97. to a linear sequence of luminances. 
  98. Uses last two arguments to set the luminance range of interest.
  99. */
  100. double SetLuminances(GDHandle myGDHandle,luminanceRecord *LP
  101.     ,int firstEntry,int lastEntry
  102.     ,double firstLuminance,double lastLuminance)
  103. /*
  104. Set a series of entries in the ColorSpec table (and the CLUT if myGDHandle is not NULL)
  105. to a linear sequence of luminances. Assume this is the entire luminance range of 
  106. interest.
  107. */
  108. SetLuminances() and SetLuminancesAndRange() both produce a linear relationship
  109. between CLUT entry and luminance over the range firstLuminance to lastLuminance,
  110. with minimum error relative to any luminance in the range lowLuminance to
  111. highLuminance. (We don't care about a small error in mean luminance.)
  112. (SetLuminances, which doesn't have these arguments, uses firstLuminance and
  113. lastLuminance to set this range.) The three DACs are linear, and the video
  114. attenuator will combine them linearly yielding a voltage V at the display input.
  115. This allows you to compute your image (e.g. a sinewave grating) and loaded to
  116. the frame store as a full scale linear function, i.e. with values ranging
  117. 0..255.  Linearization of the display and setting of contrast to whatever you
  118. want are both accomplished by one call to SetLuminances(). The contrast can be
  119. changed by calling SetLuminances() again, now with a larger or smaller luminance
  120. range. If SetLuminances is too slow for a real-time application (its
  121. computations take about 8-30 ms to compute a whole 256-entry CLUT) you can tell
  122. it to just compute, but not load the new ColorSpec table (just supply NULL in
  123. place of myGDHandle), and you can then quickly load your ColorSpec table into
  124. the CLUT later by calling LoadLuminances().
  125.  
  126. double GetLuminance(GDHandle myGDHandle,luminanceRecord *LP,int theEntry)
  127. /*
  128. If myGDHandle is not NULL then examines one entry in the actual CLUT, otherwise
  129. examines the ColorSpec table contained in *LP, and in either case returns the
  130. luminance that will be produced. Supplying an illegal entry value results in a
  131. returned value of -INF.
  132. */
  133. GetLuminance() returns the luminance of a single entry. If myGDHandle is not NULL then
  134. GetLuminance will make a low-level call to the video driver to determine what is in that
  135. CLUT entry in the actual hardware, and will return the luminance that is expected
  136. to result given the channel gains and luminance nonlinearity described in your
  137. luminanceRecord. If myGDHandle is NULL then GetLuminance
  138. returns the luminance corresponding to the entry in the ColorSpec table in your
  139. luminanceRecord.
  140.  
  141. void IncrementLuminance(GDHandle myGDHandle,luminanceRecord *LPtr,int theEntry)
  142. /*
  143. Make smallest possible increase of the luminance of one entry in the ColorSpec table.
  144. This is a way to figure out what is the lowest contrast that you can produce.
  145. */
  146.  
  147. void LoadLuminances(GDHandle myGDHandle, luminanceRecord *LP,
  148.     int firstEntry, int lastEntry)
  149. /*
  150. This just calls GDSetEntries() to load your ColorSpec table into the CLUT of
  151. your screen device. It is here simply to provide a cosmetic match to the
  152. call to SetLuminances(), for which loading the CLUT is optional.
  153. Note: if you prefer, instead of LP you may send just the address of 
  154. a ColorSpec table, cast to (luminanceRecord *), since a luminanceRecord 
  155. begins with a ColorSpec table.
  156. */
  157.  
  158. double GetV(GDHandle myGDHandle,luminanceRecord *LP,int theEntry);
  159. double VToL(luminanceRecord *LP,double V);
  160. double LToV(luminanceRecord *LP,double L);
  161. double LToL(luminanceRecord *LP,double L);
  162. Most users will never have any reason to use these routines. They are useful
  163. primarily in error analysis of the Luminance.c package.
  164.  
  165. EXAMPLES:
  166.  
  167. /* load CLUT with linear luminance range, immediately */
  168. tolerance=SetLuminances(myGDHandle,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  169.  
  170. /* load ColorSpec table with linear luminance range, then load CLUT */
  171. tolerance=SetLuminances(NULL,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  172. LoadLuminance(myGDHandle,&LR,firstEntry,lastEntry);
  173.  
  174. /* load ColorSpec table with sinusoidal luminance range, then load CLUT */
  175. for(entry=0;entry<256;entry++) {
  176.     luminance = meanLuminance*(1.0+c*sin(2.0*3.14159265*entry/256.));
  177.     tolerance=SetLuminance(NULL,&LR,entry,luminance,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  178. }
  179. LoadLuminance(myGDHandle,&LR,firstEntry,lastEntry);
  180.  
  181. /* Examine an entry in the ColorSpec table or CLUT */
  182. luminance=GetLuminance(myGDHandle,&LR,entry);    /* in CLUT */
  183. luminance=GetLuminance(NULL,&LR,entry);        /* in ColorSpec table */
  184.  
  185. NOTES:
  186.  
  187. The argument firstEntry must not exceed lastEntry. They may be equal.
  188.  
  189. The returned value, tolerance, is an estimate of the largest possible error in
  190. the luminance DIFFERENCES, taking into account the precision, accuracy (assumed
  191. to be ±1 least significant bit), and range of the DACs. (Note the returned
  192. tolerance tells you about errors in the luminance DIFFERENCES on the display.
  193. The error in ABSOLUTE luminance of the display is expected to be at most ±1 step
  194. of all the DACs, i.e. one part in 255 of full scale.)
  195.  
  196. There are no restrictions at all on firstLuminance and lastLuminance. It is
  197. reasonable to request an impossibly large luminance range, e.g. from a negative
  198. luminance up to twice the maximum luminance. SetLuminances will produce the
  199. closest possible approximation. The luminance of each entry will be clipped to
  200. fit in the range of possible luminances. Thus you will obtain the requested
  201. contrast for pixel values that aren't clipped. You can detect the clipping by
  202. the fact that the returned tolerance will be very large.
  203.  
  204. A common mistake in C programming is to use a pointer that supposedly points to
  205. a structure, without ever allocating said structure. Consider the
  206. luminanceRecord. The following declaration and call will be accepted by the
  207. compiler, but will usually have disastrous consequences:
  208.     luminanceRecord *LP;
  209.     SetLuminances(...,LP,...);        /* Bad! */
  210. The problem is that you never allocated a luminanceRecord, only a pointer. When
  211. SetLuminances starts storing information in what it thinks is a luminanceRecord it
  212. will actually be writing over a random part of memory. What you should do is:
  213.     luminanceRecord LR;
  214.     SetLuminances(...,&LR,...);        /* Good */
  215. Or you can do this:
  216.     luminanceRecord LR,*LP;
  217.     LP=&LR;
  218.     SetLuminances(...,LP,...);        /* Good */
  219.  
  220. Before using SetLuminance or SetLuminances you must initialize your luminanceRecord. 
  221. I suggest you do that by #including a file with all the parameters describing
  222. your monitor. 
  223.     luminanceRecord LR;
  224.     #include "LuminanceRecord2.h"
  225.  
  226. Here's an example of the contents of a LuminanceRecord2.h file, as produced by the
  227. program CalibrateLuminance. You must use CalibrateLuminance to calibrate
  228. your own framestore, ISR Video Attenuator, and display. 
  229.     LR.screen=2;    /* myGDHandle=GetScreenDevice(LR.screen); */
  230.     LR.date="10:20 AM Wednesday, October 10, 1990";
  231.     LR.id="5111769";
  232.     LR.name="noise";
  233.     LR.notes="denis, lights off";
  234.     LR.dpi=76.0;    /* pixels per inch */
  235.     LR.Hz=66.67;    /* frames per second */
  236.     LR.units="cd/m^2";
  237.     /* coefficients of polynomial fit */
  238.     LR.coefficients=9;    /* # of coefficients in polynomial fit */
  239.     /* L(V)=p[0]+p[1]*V+p[2]*V*V+ . . . ±polynomialError */
  240.     LR.p[0]=-2.28448e-15;
  241.     LR.p[1]=-1.47149e-13;
  242.     LR.p[2]=-8.91074e-12;
  243.     LR.p[3]=-4.47497e-10;
  244.     LR.p[4]=-1.42193e-08;
  245.     LR.p[5]=4.5072e-10;
  246.     LR.p[6]=-1.41595e-12;
  247.     LR.p[7]=-1.16427e-15;
  248.     LR.p[8]=6.69633e-18;
  249.     LR.polynomialError=  0.1974;    /* RMS error of fit */
  250.     /* coefficients of quadratic fit */
  251.     /* L(V)=q[0]+q[1]*V+q[2]*V*V±quadraticError */
  252.     LR.q[0]=5.72078;
  253.     LR.q[1]=-0.261753;
  254.     LR.q[2]=0.00204155;
  255.     LR.quadraticError=  2.1642;    /* RMS error of fit */
  256.     /* coefficients of power law fit */
  257.     /* L(V)=power[0]+Rectify(power[1]+power[2]*V)^power[3]±powerError */
  258.     /* Rectify(x)=x if x≥0, Rectify(x)=0 if x<0 */
  259.     /* Pelli & Zhang (1991) Eqs.9&10 use symbols v=V/255, alpha=power[0], beta=power[1], kappa=power[2]*255, gamma=power[3] */
  260.     LR.power[0]=0.396008;
  261.     LR.power[1]=-2.50082;
  262.     LR.power[2]= 0.035;
  263.     LR.power[3]=2.31643;
  264.     LR.powerError=  0.0863;    /* RMS error of fit */
  265.     /* coefficients of power law fit, with fixed exponent */
  266.     /* L(V)=fixedPower[0]+Rectify(fixedPower[1]+fixedPower[2]*V)^fixedPower[3]±fixedPowerError */
  267.     LR.fixedPower[0]=0.422447;
  268.     LR.fixedPower[1]=-2.68117;
  269.     LR.fixedPower[2]=0.0364552;
  270.     LR.fixedPower[3]=  2.28;
  271.     LR.fixedPowerError=  0.0796;    /* RMS error of fit */
  272.     LR.r=0.0301291;
  273.     LR.g=0.14587;
  274.     LR.b=0.824001;
  275.     LR.gainAccuracy=-0.00241191;
  276.     LR.gm=3.39315;    /* The monitor's contrast gain. */
  277.     LR.VMin=  0;    /* minimum nominal voltage that can be loaded into DAC */
  278.     LR.VMax=255;    /* maximum nominal voltage that can be loaded into DAC */
  279.     LR.LMin=    0.40;    /* luminance at VMin */
  280.     LR.LMax=   74.74;    /* luminance at VMax */
  281.     LR.LBackground=  12.404;    /* background luminance during calibration */
  282.     LR.VBackground=155;    /* background number used during calibration */
  283.     LR.rangeSet=0;    /* indicate that range parameters have yet to be set */
  284.     LR.L.exists=0;    /* indicate that luminance table has yet to be initialized */
  285. Note that these parameters describe fits of three functions to the gamma function.
  286. You may omit any or all of these fits, but should document it by setting the
  287. corresponding error terms to infinity (=1.0/0.0). If you omit all of them then
  288. you must supply a table describing the gamma function, e.g.
  289.     LR.L._VMin=DoubleToMilli(0.);    /* if possible, restrict to just the monotonic range */
  290.     LR.L._VMax=DoubleToMilli(255.);    /* if possible, restrict to just the monotonic range */
  291.     LR.L._dV=(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1);
  292.     LR.L.latestIndex=-1;            /* invalid latestIndex, so hunt will start from scratch */
  293.     LR.L.exists=luminanceSet;        /* mark table as valid */
  294.     LR.L._L[0]=DoubleToMilli(0.57);
  295.     LR.L._L[1]=DoubleToMilli(0.57);
  296.     ...
  297.     LR.L._L[255]=DoubleToMilli(101.13);
  298. Normally this table is synthesized at run time, from one of the formulaic descriptions,
  299. but you may prefer to supply it directly, possibly using the raw measurements,
  300. and not bother with any fitting. The formulas are not used if the table is supplied.
  301.  
  302. To fill in the numbers above, you have to do two calibrations. (This is what the
  303. CalibrateLuminance program does.)
  304.  
  305. 0. Before starting, call GDLinearGamma(). (Note that GDOpenWindow 
  306. automatically calls GDLinearGamma for you.) All calibrations should be done
  307. with the ISR Video Attenuator in place.
  308.  
  309. 1. Measure the luminance versus "voltage" nonlinearity of your monitor.
  310. I use a separate program "CalibrateLuminance" to measure the screen luminance at values
  311. of V from 0 to 255. For this calibration you load equal values into Red, Green, and Blue
  312. lookup tables. I use quadratic, polynomial, and power law fits. See Pelli & Zhang (1991).
  313.  
  314. You must set LR.LMin and LR.LMax to the luminances at LR.VMin and LR.VMax.
  315.  
  316. The following program fragment allows you to measure the screen luminance as a function
  317. of nominal voltage.
  318.  
  319. int V,i,done;
  320. double a;
  321.  
  322. #include "LuminanceRecord1.h"
  323. oldGDHandle=GetGDevice();
  324. GetPort(&oldWindowPtr);
  325. LR.screen=1;
  326. myGDHandle=GetScreenDevice(LR.screen);    /* screen 1 */
  327. myCWindowPtr=GDOpenWindow(myGDHandle);    /* Open a full-screen window with explicit colors */
  328. SetPort((WindowPtr)myCWindowPtr);        /* Tell QuickDraw which window */
  329. SetGDevice(myGDHandle);                    /* specify which device's color table */
  330. PmBackColor(1);                            /* pick a color table entry */
  331. EraseRect(&myCWindowPtr->portRect);        /* fill whole window with that color */
  332. SetPort(oldWindowPtr);
  333. SetGDevice(oldGDHandle);
  334. LR.L._VMin=DoubleToMilli(0.);            /* if possible, restrict to just the strictly monotonic range */
  335. LR.L._VMax=DoubleToMilli(255.);
  336. done=0;
  337. for(i=0;;i++){
  338.     LR.L._dV=ceil(MilliToDouble(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1));
  339.     V=MilliToLong(i*LR.L._dV);
  340.     if(V>=MilliToLong(LR.L._VMax)){
  341.         V=MilliToLong(LR.L._VMax);
  342.         done=1;
  343.     }
  344.     LR.table[1].rgb.red        =V<<8;    /* Set color table entry 1. */
  345.     LR.table[1].rgb.green    =V<<8;
  346.     LR.table[1].rgb.blue    =V<<8;
  347.     LoadLuminances(myGDHandle,&LR,1,1);    /* Copy color table entry 1 to CLUT */
  348.     printf("%3d Please measure screen luminance now, in %s, and type in:",V,LR.units);
  349.     gets(string);
  350.     sscanf(string,"%lf",&a);
  351.     printf("%6.2g %s\n",a,LR.units);
  352.     LR.L._L[i]=DoubleToMilli(a);
  353.     if(done)break;
  354.     if(LR.L._L[i]<=LR.L._L[0]){    /* restrict to just the strictly monotonic range */
  355.         LR.L._VMin=LongToMilli(V);
  356.         LR.L._L[0]=LR.L._L[i];
  357.         i=0;
  358.     }
  359. }
  360. LR.L.latestIndex=-1;            /* invalid latestIndex, so hunt will start from scratch */
  361. LR.L.exists=luminanceSet;        /* mark table as valid */
  362.  
  363. 2. Measure the gains of the three inputs of your ISR Video Attenuator. You could
  364. do this by using an oscilloscope to measuring the voltage at the input to the monitor.
  365. Instead, I measure the resulting luminance and use LToV() to infer the voltage.
  366. Vary one DAC while holding the other two fixed at 255 and measure the luminances. 
  367. Use the function LToV() to convert back to a "voltage" and compute the gain of the
  368. varying DAC. Note that the three DACs on your framestore will generally have gains that
  369. match to only ±5%. This calibration is measuring the overall gain of each of the three
  370. pathways, including your DACs and the ISR Video Attenuator.    
  371.  
  372. LIMITATIONS:
  373.  
  374. It is imperative that you call GDLinearGamma() at some time before using
  375. SetLuminances. SetLuminances ASSUMES that no gamma correction takes place. You
  376. only need to call GDLinearGamma once. It will stay that way until the next time
  377. you restart your computer. Incidentally, GDOpenWindow() calls GDLinearGamma for
  378. you.
  379.  
  380. The luminance calibration data that you supply to SetLuminances must also have
  381. been collected with no gamma correction, i.e. AFTER calling GDLinearGamma.
  382.  
  383. The reason that gamma correction is not allowed is that it would result in a
  384. nonlinear transformation of the three channels BEFORE they are combined in the
  385. Video Attenuator.
  386.  
  387. The measurement of the gains of the three pathways must be made using YOUR
  388. framestore. The gains of your three DACs will in general be different from each
  389. other, and different from framestore to framestore. Brooktree guarantees the
  390. matching of the gains of the three DACs on their chip to only ±5%.
  391.  
  392. The luminance record may include either a table of luminance calibrations. If it
  393. is not supplied then it will be synthesized from the parameters of the
  394. polynomial or power law fit. If the power, polynomial, or quadratic fit
  395. parameters are not supplied then the appropriate LR.powerError,
  396. LR.polynomialError, or LR.quadraticError field should be set to infinity
  397. (=0.0/1.0).
  398.             
  399. The whole package is at present restricted to grayscale.
  400. There is no provision for linearizing a color monitor. In particular it is
  401. assumed that the the DACs are linearly combined BEFORE the display nonlinearity.
  402. Linearizing luminance on a color display would need to allow for three different
  403. nonlinear transformations.
  404.  
  405. EXPLANATION OF THE CODE:
  406.  
  407. Physically your framestore transforms each CLUT entry number to a voltage that
  408. will be linearly related to the number. The three output voltages will be
  409. combined by the ISR Video Attenuator to produce a single voltage which drives
  410. the video monitor. Finally, the display nonlinearly transforms V to produce a
  411. luminance L. My convention for "measuring" the "voltage" V at the input of the
  412. monitor is that V=0 when all three DACs are set to zero and V=255 when all three
  413. DACs are set to 255. (This will be linearly related to volts measured, with a
  414. voltmeter, at the output of the Video Attenuator.) The virtue of the attenuator
  415. is that it allows us to produce nonintegral values of V.
  416.  
  417. There are two givens:
  418. 1. DACs are inaccurate. A good DAC may be specified to be merely monotonic. For
  419. purposes of computing the returned tolerance value, I follow the Brooktree DAC
  420. specification of ±1 LSB error in integrated linearity and assume that the error
  421. in any luminance difference is at most ±1 LSB.
  422. 2. We want to minimize the error in representing the waveform, but are not
  423. particularly worried about the exact value of the mean luminance. Thus, if we
  424. have DACs with different gains we may fix the coarse DACs to set the mean
  425. luminance and vary the fine DACs to produce the linear range of luminances
  426. requested.
  427.  
  428. Here's the strategy. We want to cover the range L0 to Ln with the smallest
  429. possible error in L-L0.
  430.  
  431. Steps 1 to 5 happen in SetLuminanceRange:
  432. 1. Sort the DACs by gain g, where gain is defined as the change in V when
  433. a single DAC is increased from 0 to 255, so g0+g1+g2=1. Let the gains be g0>g1>g2. 
  434. 2. Transform the luminance range to a nominal voltage range lowV and highV.
  435. 3. Decide which DACs should be variable and which should be fixed,
  436. so as the minimize the tolerance in the luminance increments.
  437. 4. Temporarily set the variable DACs to their midpoints. Now set the fixed DACs to
  438. most accurately represent the midpoint of the nominal voltage range, (lowV+highV)/2.
  439. 5. If necessary, compute a small luminance offset LShift to bring the requested range 
  440. lowV to highV into the range attainable by the variable DACs. (This is necessary
  441. because the centering in step 4 may not be precise enough.)
  442.  
  443. Step 6 happens in _SetLuminance:
  444. 6. Set the variable DACs in the ColorSpec entry so as to most accurately represent
  445. L+LShift.
  446.  
  447. SetLuminances(), SetLuminancesAndRange(), and SetLuminance() all call _SetLuminance().
  448.